Skip to content

fix(B2): BEP 계산 사용자 입력값 연결 및 월매출 단위 수정#112

Open
soooojinn-prog wants to merge 343 commits into
mainfrom
feature_sj3
Open

fix(B2): BEP 계산 사용자 입력값 연결 및 월매출 단위 수정#112
soooojinn-prog wants to merge 343 commits into
mainfrom
feature_sj3

Conversation

@soooojinn-prog

Copy link
Copy Markdown
Collaborator

Summary

  • bep.py: 업종별 재료비율 실제 평균값으로 업데이트, 인건비 제외, initial_capital/monthly_rent 파라미터 추가
  • interface.py: quarterly_avg ÷ store_count ÷ 3 으로 월매출 환산 수정 (monthly_sales는 분기합계 DB 확인)
  • graph.py: state에서 initial_capital/monthly_rent_budget 꺼내 cost_config 구성 후 전달
  • synthesis.py: tcn_block 조건 버그 수정, 문법 오류 수정

변경 배경

  • BEP가 초기자본금·임대료 하드코딩(1.3억/200만)으로 사용자 입력을 무시하던 문제 해결
  • monthly_sales DB 컬럼이 분기합계(3개월치)임을 확인 → ÷3 누락으로 월매출 3배 과대계산 버그 수정
  • 인건비는 운영 규모 편차가 크므로 BEP 계산에서 제외 (C1 면책 문구 추가 요청 예정)

Test

  • pytest tests/test_synthesis_regression.py → 4개 PASS
  • BEP: 상암동 커피-음료 기준 월매출 3,373만원 / BEP 8개월 (자본금 1.5억, 임대료 400만)

Knockcha and others added 30 commits April 22, 2026 12:21
- scripts/capture_sections.py: headless Chromium 1440x900@2x, fake_auth localStorage 주입으로 ProtectedRoute 우회, RUN SIMULATION 클릭 후 #section-01~15 element screenshot + 풀페이지
- IntegratedReport 각 섹션에 id="section-NN" + data-section anchor (selector용, UI 영향 없음)
- scripts/README.md: 실행·트러블슈팅 가이드
- screenshots/.gitkeep: 출력 디렉터리 추적

현재 시점 검증: 16장 캡처 성공, § 내부 식별자 UI 노출 0건 확인.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- python-jose[cryptography] 의존성 추가
- backend/src/config/settings.py: JWT_SECRET_KEY / JWT_ALGORITHM / JWT_EXPIRE_MINUTES 3 필드 + dev-fallback
- backend/src/services/jwt_auth.py: create_access_token / decode_token / get_current_user FastAPI dependency + UserContext dataclass
- frontend/src/api/client.ts: localStorage.spotter_auth.token → Bearer 자동 주입 + 401 시 토큰 드롭 (재로그인 UX 유지용 user/brand는 유지)
- frontend/src/auth/AuthContext.tsx: token state 추가, login(user, brand, token?) 시그니처 확장, stale JSDoc 정리
- .env.example: JWT 3 환경변수 placeholder (운영 전 openssl rand -hex 32로 교체 필요)

기존 엔드포인트는 Bearer 요구로 전환하지 않음 (회귀 방지).
실제 token 발급은 다음 커밋의 main.py login 엔드포인트 연결에서.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
매니저가 예비 가맹점주(client_name)별로 시뮬 결과를 보관/재열람.

[Backend — AGENTS.md상 B1 영역, 강민 승인한 풀스택 범위]
- alembic: a2f3b6d84e9c 마이그레이션 (simulation_history 테이블 + pg_trgm + 4 인덱스)
- database/models.py: SimulationHistory ORM
- schemas/simulation_history.py: Pydantic (Create/ListItem/Detail/Response)
- services/simulation_history_service.py: create/list/get/delete CRUD (동기 엔진, auth.py 패턴 재사용)
- api/simulation_history.py: POST/GET/GET:id/DELETE 4 endpoints, JWT Bearer 필수
- main.py: /auth/login·/auth/manager/login 응답에 access_token 주입 + simulation-history router 등록

[Frontend]
- types/simulationHistory.ts: TS 타입 + formatDocumentId 헬퍼 (SPTR-000142 / SPTR-DRAFT-xxxx)
- api/client.ts: 4 함수 추가 (save/list/detail/delete, Bearer 자동 주입)
- hooks/useSaveSimulation: 저장 mutation + UI 친화 에러 메시지
- hooks/useSimulationHistory: 목록 query + optimistic delete
- hooks/useSimulationDetail: 단건 조회
- components/SimulationHistory/*: SaveButton/SaveDialog (Framer Motion 커스텀), HistoryCard/Filter/List
- pages/ManagerDetail: /hq/managers/:id 4 탭 (기본정보/담당권역/시뮬이력/활동로그), master/self 접근 분기
- pages/SimulationHistoryDetail: /dashboard/history/:id 저장된 시뮬 15 섹션 재현 (읽기 전용)
- App.tsx: SaveDialog 마운트 + savedHistoryId state + Document ID 격상 로직 + 2 라우트 + "내 이력" 헤더 버튼
- LoginPage: access_token 전달
- HiddenPDFTemplate: savedHistoryId prop 받아 PDF 표지에 SPTR-000142 표시

[Phase 2 미구현 명시]
- 팀장이 하위 매니저 이력 조회 (master 뷰 탭 disabled + tooltip)
- 상세 페이지에서 직접 다시 실행 (현재는 /simulator 이동만)
- 상세 페이지 PDF/XLSX 내보내기 (alert 안내)

강민님이 배포 전 실행: `alembic upgrade head` + `openssl rand -hex 32`로 JWT_SECRET_KEY 교체.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pth-agent

# Conflicts:
#	frontend/src/App.tsx
[1] demographic_depth: 업종 특화 데이터 없을 때 전체 업종 fallback 추가
    - CS100007(치킨) 등 데이터 없으면 전체 업종 집계로 재시도 → unknown 제거
    - fallback 사용 시 narrative에 "전체 업종 기준 분석" 주석 자동 삽입

[2] district_ranking: 경쟁밀집도 가중치 5% → 15% 상향
    - SEMAS 포화도 높은 동(치킨 많은 공덕동 등) 랭킹 하락 반영
    - 매출 가중치에서 10%p 차감, 최소 5% 보장

[3] main.py + frontend: 추천 동 전체(winner+top3) 경쟁업체 지도 핀
    - _collect_all_competitor_locations(): winner+top3 각 500m 경쟁업체 수집·중복제거
    - all_competitor_locations 필드로 응답 반환
    - App.tsx: allCompetitorLocations 우선 사용, fallback은 기존 single-district 샘플
- commercial_intelligence: _saturation_bucket 임계값 현실화
  sparse 0~2 / low 3~5 / medium 6~10 / high 11~20 / saturated 21+
  (기존 low=5~14, medium=15~29로 7개도 'low'로 잘못 표시되던 버그 수정)
- demographic_depth: 캐시 키 v2→v3 (fallback 로직 적용 위해 stale 캐시 우회)
- competitor_intel: 캐시 키에 버전 prefix 추가 (v2:)
- App.tsx: 상세 테이블(가맹점 간섭도) 데이터 소스를
  allCompetitorLocations(winner+top3 전체 동) 우선 사용하도록 교체
  (기존: winner 단일 동 samples만 표시 → 공덕만 나오던 버그 수정)
…rd_flpop 전수)

◆ ABM 시뮬레이션
- Policy Generator 기반 v12 baseline 확정
- backend/src/simulation/* 개선 (agents / runner / world_loader / policy_generator)

◆ DB 정리
- dong_mapping 구→신 코드 교정 (11440555 등)
- living_population dong_name shift 수정
- mapo_resident_pop 표기 통일 (망원제1동→망원1동)
- jeonse_monthly_rent dong_code 10자리 정규화
- seoul_adstrd_flpop 마포만 → 서울 25자치구 (448→11,900)
- naver_trend_monthly/quarterly 2019-2024 → 2019-2026

◆ 신규 테이블
- customers (매장 방문 고객)
- simulation_history (pg_trgm GIN + FK manager_users ON DELETE CASCADE)

◆ Alembic
- multi-head 정리 (93cecfb0a772 merge)
- a2f3b6d84e9c add_simulation_history
- 7ea1945766a2 FK + 중복 인덱스 제거

◆ 문서
- docs/db-schema.md 섹션 30(customers), 31(simulation_history)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- closure_risk/model.py: fc_head → fc 교체 (TCNForecaster.forward()와 연결)
- closure_risk/model.py: 전이학습 필터 fc_head.* → fc.* 로 수정
- closure_risk/train.py: TCN scaler 저장 추가 (closure_risk_tcn_scaler.pkl)
- closure_risk/predict.py: 저장된 scaler 로드·사용, input_size 기본값 33→34
- closure_risk/predict.py: _mock_result()에 feature_key·summary 필드 추가
- closure_risk/data_prep.py: LGBM_FEATURES 10→15개 확장 (rent_1f_lag1 등)
- closure_risk 재학습: LightGBM=0.494, TCN=0.506 앙상블
- shap_analysis.py: explain_prediction() deprecated 제거, TCN SHAP summary 추가
- shap_analysis.py: _generate_tcn_summary() 자연어 요약 템플릿 20개 피처
- closure_risk/predict.py: _FEATURE_KO 4개 추가, _generate_risk_summary() 추가
- frontend/types/index.ts: ShapResult·ClosureRisk에 summary?: string[] 추가
- frontend/ShapChart.tsx: SHAP 차트 하단 자연어 요약 카드 렌더링
- frontend/App.tsx: 폐업위험도 top_signals 하단 자연어 요약 문장 렌더링

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
[Backend — Phase 2/3]
- AbmSimulationRequest 에 spot_lat/spot_lon Optional 필드 추가
- new_store_spec 에 클릭 좌표 포함, runner 가 신규 매장 주입 가능하게
- 응답 신규 필드: new_store_visits, new_store_revenue, new_store_visit_share_pct
- Redis 캐시 (abm_sim:{sha256} key, TTL 1h, cached 플래그)

[Frontend — Phase 4]
- AbmPersonaMap: AgentVacancySpot 인터페이스 + vacancySpots/onSpotClick/onClearResult props
- 지도 우상단: 공실 스팟 TOP 8 선택 패널 (listing_count 정렬, 타겟 동 강조)
- 결과 오버레이: 방문 점유율(%) + 일 매출(원) 2-grid 카드 + "← 뒤로" 버튼
- App.tsx: onSpotClick 콜백 (스팟 좌표·동으로 /api/simulate-abm 호출)
- App.tsx: onClearResult 콜백 (setAbmResult(null) 로 스팟 화면 복귀)

브랜치 전환 시 누락됐던 어제 dev 작업 복원.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- main.py: dev 베이스 채택 + _collect_all_competitor_locations 패치
- App.tsx: ABM 에러처리 dev 버전(HTTP상태/unavailable/error 세분화) 채택
- AbmPersonaMap.tsx: dev 공실스팟 useEffect + 우리 customerProfileDistRef(pickType dist 전달) 통합

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
IM3-202: 경쟁업체 지도 핀·AI VERDICT 가독성·인구통계·포화도 수정
…pth-agent

# Conflicts:
#	backend/alembic/versions/a2f3b6d84e9c_add_simulation_history.py
#	backend/src/database/models.py
#	backend/src/main.py
#	frontend/src/App.tsx
`alembic upgrade head` 실행 시 psycopg2가 DSN 문자열 파싱 중
UnicodeDecodeError를 던지는 이슈 (Windows 한국어 로케일 + 재한국 PostgreSQL 메시지).

원인: psycopg2의 URL parser가 Windows cp949/UTF-8 경계에서 깨짐.
해결: SQLAlchemy make_url로 URL을 분해한 뒤 connect_args로 명시 전달.
psycopg2 자체 URL 파싱 경로를 건너뛰므로 로케일 무관하게 동작.

리눅스/맥 사용자엔 완전 무해 (동일 connect_args 사용).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
전역 apiClient.timeout = 120,000ms(2분)은 빠른 엔드포인트엔 적합하지만
/simulate 는 LLM 파이프라인 경유라 캐시 miss 시 3~5분 소요.
runSimulation 함수에서만 timeout 600,000ms 로 override.

다른 API(simulation-history/auth/population)는 여전히 2분 유지 —
이 엔드포인트들이 2분 초과하면 진짜 문제 신호이므로 짧은 timeout 유지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- parse_pdfs.py: 타법 참조 조문 필터링 (유령 조문 112개 제거, 3775→3663 청크)
- retriever.py: HyDE 동의어 17개 추가, LEASE_LAW_STRICT_SOURCES, reranker 제거
- legal.py: 조문 요약 개선 (_summarize_article 불완전 문장 처리)
- postgres.py: pool_size 3→10 (7개 에이전트 병렬 실행 대응)
- district_ranking.py: DB 세마포어 추가 (16개 동 동시 조회 보호)
- 벤치마크: Exact-match 63.6%, LLM-judge 82.7% (v1 대비 +8.6%p/+10.7%p)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1) runSimulation() 언래핑 로직:
   dev의 main.py가 /simulate 응답을 {status: 'success', data: {...result}} 래퍼로 변경.
   프론트는 response.data를 바로 SimulationOutput으로 반환하고 있어
   rawSimResult가 {status, data} 형태로 저장 → 모든 섹션 "데이터 없음".
   status='success' 감지 시 body.data 언래핑, error면 throw. 구 포맷도 호환.

2) DB pool 3→5, max_overflow 5→10:
   Redis 캐시 미기동 시 parallel_analysis 7 agent × 16동 DB 호출이
   pool=3+5=8 제한으로 QueuePool exhausted → ConnectionDoesNotExistError 연쇄.
   RDS max_connections=81 내에서 2~3명 동시 작업 감당 가능한 수준으로 조정.
   Redis 정상 가동 시엔 여전히 여유.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
문제: cd backend 후 uvicorn 실행하면 dotenv가 backend/.env 를 찾다 실패.
→ POSTGRES_URL 미주입 → biz_mapper.DB_URL 이 localhost fallback 사용
→ /auth/login 이 RDS 대신 localhost PostgreSQL 붙으려다 인증 실패
→ 사용자엔 "아이디 또는 비밀번호 틀림" 으로 노출

수정: config/settings.py + main.py 의 load_dotenv 가 __file__ 기준으로
repo root .env 를 명시 로드. cwd=repo-root 에서 실행하든 cwd=backend 에서
실행하든 동일 결과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- data_prep.py: YEAR_BASE/YEAR_SCALE 상수 추가 (2019, 10.0)
  quarter_enc (N×2 → N×3): sin/cos/year_norm 구조로 확장
  (동,업종)별 identified_ratio 딕셔너리 계산 (monthly_sales=0 제외, clip 적용)
  save/load_mappings: identified_ratios + year_max 포함
  _quarter_encoding() 데드코드 제거

- model.py: input_dim 10 → 11 (year_norm 피처 추가)
  Architecture/docstring/주석 동기화

- train.py: prepare_training_data() 6-tuple 언패킹
  save_mappings(identified_ratios, year_max) 전달

- predict.py: IDENTIFIED_RATIO 상수 제거 → 딕셔너리 조회(fallback 0.8637)
  predict()에 year 파라미터 추가 (기본값: 현재 연도)
  year > year_max+2 시 UserWarning 발동
  YEAR_BASE/YEAR_SCALE import, datetime/warnings import 추가

재학습: val_loss=0.002961 (Epoch 126 early stopping)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	backend/src/database/postgres.py
- InsightsGrid: 법률 타입명 한글 매핑, 위험도 안전/주의/위험 통일
- LegalDrawer: ��로어 제목 한글, 위험도 배지 한글, AI권고 최상단 배치
- PrimaryKPIs: 법률안전도 KPI에 안전/주의/위험 분포 표시
- 벤치마크 리포트 오탈자 수정

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
IM3-A2: RAG v3 + 법률 UI 한글화 + 커넥션 풀 안정화
문제: /simulator 결과 → 다른 페이지 이동 → 복귀 시 local state(reportState/simResult/rawSimResult) 소실 → 통합 리포트 부분만 렌더되거나 입력 화면으로 복귀.

원인: SimulatorDashboard local useState 에 시뮬 결과 중복 저장. useSimulationStore.result 는 persistent 지만 rehydration 로직 없음.

규칙 도입:
- R1: 서버 응답 데이터는 Zustand store 만 Single Source of Truth
- R2: 컴포넌트 마운트 시 store → local rehydrate
- R3: 뷰 플래그도 가급적 store/URL
- R4: UI-only 상태(dialog open 등)만 local 허용
- R5: dismiss 액션은 store level 에서 실행

구현:
- simulationStore: savedHistoryId 필드 추가 + setSavedHistoryId action
  (startSimulation / cancelSimulation / dismissResult 시 null 리셋)
- App.tsx: toSimResultViewModel(simRes) 모듈-level 함수 추출
  (runSim 성공 경로와 마운트 rehydrate 경로가 동일 로직 재사용)
- App.tsx: SimulatorDashboard 마운트 useEffect 추가 — store.result 있고 local 비어있으면 복원
- App.tsx: popstate 핸들러가 setReportState('idle') + dismissResult() 호출 — 뒤로가기 시 store 도 명시 초기화
- App.tsx: savedHistoryId 는 local useState 제거, store 에서 파생 (SaveDialog 저장 시 store.setSavedHistoryId 호출)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Knockcha and others added 30 commits April 27, 2026 12:09
…nt / zombie

코드 리뷰(superpowers:code-reviewer) 발견 critical 2 + high 3 + medium 2 일괄 수정.

## UI fix
- TabbedDashboard sticky header top-0 → top-24 md:top-28
  글로벌 header(fixed h-24 z-50)와 sticky(z-40) 색차/틈 해소

## 타입 contract (#1)
- DistrictComparison.revenue / cannibalization → number | null
  코멘트는 nullable인데 실제 타입은 number 비-nullable이었음 (절반만 적용된 상태)
  main.py 폴백 None 5필드 모두 정렬
- viewmodels/simResult.ts SimResult.comparison 인라인 타입 동기화

## useCallback stale closure (#2, #3)
- handleDownloadExcel deps에 savedHistoryId 추가
  → 시뮬 저장 직후 Excel 다운로드 시 stale ID 박힌 파일 생성 방지
- runSim deps에 brand?.brand_name + saveSim 추가
  → 로그아웃 후 재로그인 시 새 brand 정보 미반영 방지

## Lazy mount (#4)
- SpotterAgentWorkflow를 isWorkflowOpen 조건부 마운트로 변경
  drawer 닫혀있을 때 lazy chunk fetch + 내부 setTimeout 3개 시작 방지
  (cleanup 함수는 이미 정상 — always-mount 때문에 작동 안 한 것)

## 좀비 클러스터 제거 (#6)
6개 파일 일괄 삭제 (import 0건 검증 후):
- src/contexts/SimulationContext.tsx (zustand simulationStore로 대체됨)
- src/hooks/useSimulation.ts (위와 동반)
- src/pages/AnalysisDashboard.tsx (App.tsx에서 의도적 주석 처리됐던 dead 파일)
- src/pages/MirofishDashboard.tsx (PoC 잔재)
- src/components/MirofishLogPanel.tsx
- src/components/DirectOperationBanner.tsx

부수 정리 (#7):
- App.tsx의 stale 주석 "// import AnalysisDashboard ... JSX 에러" 제거
- main.tsx의 "AnalysisDashboard 등 특정 페이지" → "특정 페이지" 일반화

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전 fix(3af5360)는 sticky의 위치(top-24 md:top-28)는 맞췄지만 배경이
반투명(bg-[#0C0B0A]/90 + backdrop-blur-2xl)이라 스크롤 시 지도·차트 등
하단 콘텐츠가 sticky 뒤로 비치는 "레이어 누수" 발생.

수정:
- bg-[#0C0B0A]/90 + backdrop-blur-2xl → bg-[#0C0B0A] (불투명 solid)
- z-40 → z-50 (글로벌 header와 동일 레벨, SimulatorDashboard 컨테이너 z-40 위)

위치(top-24 md:top-28)는 그대로 — 글로벌 header(fixed h-24) 바로 아래
인접 배치 유지. Cockpit 디자인에서 두 header가 색차/blur로 어색해지는
문제도 함께 해소.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
대시보드 sticky header가 스크롤 시 화면 중간에 떠다니는 현상의 진짜 원인.

CSS spec: 'transform: none'이 아닌 값을 가진 ancestor는 자식 fixed/sticky의
containing block을 그 ancestor로 한정시킴. translateY(0)은 시각적으론 0이지만
'none'이 아니라서 SimulatorDashboard 컨테이너(animate-[fadeSlideIn] 적용)가
sticky의 containing block이 되고, sticky top-* 값이 viewport가 아닌 ancestor
기준으로 동작 → 화면 중간 어정쩡한 위치에 stick.

수정: keyframe to-state를 transform: none으로 변경.
- 시각 효과 동일 (translateY 0 = none)
- 애니메이션 종료 후 ancestor에 잔류 transform 없음
- 자식 sticky가 viewport 기준 정상 동작 → top-24 md:top-28이 화면 96/112px로 정확히 적용

이전 fix(top-24 md:top-28 + solid bg + z-50)이 의도대로 작동하기 시작.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 트랙 A-1 / B2 영역을 TCN 기반으로 전면 교체 (LSTM은 레거시로 보존)
- B2 담당 디렉토리에 models/tcn_forecast, models/closure_risk, models/interface.py 추가
- 세부 Task에 폐업위험도(LightGBM+TCNClassifier 앙상블), 폐업률, BEP, 통합 인터페이스 4행 신설
- 5개 파트(매출/폐업위험도/폐업률/BEP/SHAP)가 ModelOutput.generate를 통해 LangGraph로 통합되는 파이프라인 다이어그램 추가
- TCNForecaster vs TCNClassifier Feature Extractor 전이학습 구조 비교표 추가
- "LSTM v9 상태" 섹션을 "TCN 최종 상태"로 갱신 (input_size=34, MAPE 15~16%)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- models/lstm_forecast/data_prep.py: ExcludedComboError 커스텀 예외 클래스 추가
  (EXCLUDE_COMBOS 조합 — 염리동×중식, 성산1동×제과 추론 차단용)
- models/tcn_forecast/predict.py: EXCLUDE_COMBOS 체크 추가, import 보완
- models/closure_risk/predict.py: EXCLUDE_COMBOS 체크 추가, import 보완
- models/interface.py: ExcludedComboError import, generate() 선제 차단,
  3개 except 블록 re-raise, is_mock 플래그 전 함수 통일
- models/revenue_predictor/predict.py: is_mock 플래그 통일 (정상/mock/fallback 구분)
- models/revenue_predictor/bep.py: is_mock 플래그 추가
- validation/backtest_closure.py: 오탈자 수정

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- scripts/imputed_to_sales_schema.py — imputed_*_v3.csv → district_sales 스키마 어댑터
- models/lstm_forecast/data_prep.py — sales_csv_override + train_cutoff_quarter 인자 추가
- models/tcn_forecast/train.py — --sales-csv, --train-cutoff-quarter CLI 인자 추가
- validation/experiments/tcn/compare_imputed.py — 3 모델 백테스트 결과 비교
- tests/ — 8 tests 모두 PASS
- 3 모델 동일 환경(34피처, cutoff=20241, seed=2026) 백테스트 산출
  Original-rebuild MAPE 15.7%, TCN-A 16.3%, TCN-B-rebuild 15.3%
- .gitignore: 100MB+ 대용량 파일(sales_imp_seoul.csv, seoul_migration), v4 미완성 산출물 제외
- docs/abm-simulation/tcn-imputed-comparison-report.md, -test-cases.md
- docs/superpowers/plans/2026-04-25-tcn-imputed-vs-original-comparison.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ABM Phase I/Full PSE-3 검증 (validation/abm_vs_grid_*.py 4종 + phase_full/phase_i 결과 8개)
- docs/abm-simulation/OVERVIEW.md, ceiling-analysis.md 신규
- sim-mode-matrix, sprint-2026-04-week-ceiling-push, vacancy-injection 갱신
- backend/src/database/seed.py — vacancy_pse 분기/연 단위 출력 보정
- scripts/extract_mapo_migration.py — KOSIS 시군구 인구이동 추출
- 회고 docs/retrospective/2026-04-25.md, 2026-04-26-abm-product-iteration.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
사용자 요청 — 박스 형태 조정 + "더 정확한 분석을 원하시나요?" 토글 제거 +
ADVANCED 항목을 옆으로 항상 펼침 + RUN 버튼 우측 하단 박스로 분리.

레이아웃:
- Dashboard body: flex-row max-w-7xl → grid lg:grid-cols-12 max-w-[1650px]
- 좌 패널 lg:col-span-5: SIMULATION CONTROLS
  · 내부 grid grid-cols-1 md:grid-cols-2 gap-6
  · 좌 col: BASIC 입력 (브랜드/카테고리/예산/자본/반경/동 선택 등)
  · 우 col: ADVANCED 입력 (매장면적/객단가/시간대/타겟) — 항상 펼침
- 우 wrapper lg:col-span-7 flex-col gap-6
  · 위 박스: 기존 idle silhouette / loading progress (디자인 100% 유지)
  · 아래 박스: RUN SIMULATION (분리된 별도 박스)

RUN 박스:
- 좌측 텍스트: "시뮬레이션 실행 / 좌측 입력값을 바탕으로 AI 분석 엔진을 가동합니다 · 약 20초 소요"
- 우측 버튼: 기존 RUN 버튼 디자인 그대로 (gradient/shadow/hover scale)
- onClick={runSim}, disabled={reportState === 'loading'} 동작 유지

상태 처리:
- result 진입 시 좌 패널 hidden + 우 wrapper에 lg:col-span-12 자동 확장
- absolute inset-0 z-40 결과 화면 그대로 (TabbedDashboard 호출 그대로)
- showAdvanced state + setShowAdvanced 사용처 모두 제거 (33줄)
- 미사용 Settings lucide 아이콘 import 제거

원칙 준수:
- 입력 컴포넌트(HybridSliderInput / 시간대 multi-select / 슬라이더 등) props·내용·색깔 1자도 변경 없음
- 색감/효과/그림자/그라디언트 모두 그대로
- 디자인 변경 금지 — 위치/레이아웃 wrapper만 변경

App.tsx 4744 → 4768줄 (+24, layout wrapper 추가 - showAdvanced 제거 상쇄).
tsc + build 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
IM3-243: TCN imputed 비교 + ABM sprint + AGENTS.md 갱신 (dong-fk-followup)
직전 push(86ee62a)에서 BASIC|ADVANCED 2-col 분리 적용 후 사용자 피드백:
ADVANCED column 안의 검은 박스(bg-[#1e1b18]/50 border-[#3a3633] p-4 rounded-xl)
때문에 우측만 시각적으로 길어지고 좌측 BASIC column 아래에 거대한 공백 발생.

수정:
- 검은 박스 wrapper 제거 → 단순 `<div className="space-y-6">`로 변경
- 닫는 </div> 1개 제거 + indent 정렬
- ADVANCED 항목들이 BASIC과 같은 패널 톤(${panel})으로 노출 → 시각 일관성

효과:
- 좌측 패널 전체 톤 통일 (검은 inset 박스 제거)
- 우측 column 시각 길이 축소 → 좌측 공백 감소

추가 조정(타겟 고객 프로필 grid 밖 분리 등)은 사용자 검증 후 결정.
입력 컴포넌트 props·내용·색깔 모두 그대로 유지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
좌측 패널 BASIC|ADVANCED 2-col 분리 후 잔존 불균형 해소:
ADVANCED column 안의 "타겟 고객 프로필"(~250-300px, 5필드 그리드)이
가장 긴 영역이라 ADVANCED column이 BASIC보다 길어지는 주범.

수정:
- 타겟 고객 프로필 div(약 153줄) ADVANCED column에서 분리
- grid 안의 col-span-2 새 row로 이동 (BASIC + ADVANCED 다음 줄)
- 외곽 className: 'pt-4 border-t border-[#3a3633]/50'
  → 'md:col-span-2 pt-6 mt-2 border-t border-[#3a3633]'
- 면책 문구는 ADVANCED column 마지막에 그대로 잔류

효과:
- BASIC | ADVANCED 두 column 길이 거의 균등 (좌측 거대 공백 해소)
- 타겟 고객 프로필이 가로 폭 전체를 사용 → 5필드 그리드 가독성 ↑

입력 컴포넌트 props·className·onClick 1자도 변경 없음.
상태 hook(targetAgeGroups/Gender/TimeSlots/DayType/MonthlySales) 그대로.

App.tsx 4768 → 4764줄. tsc + build 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
토글이 슬라이더 항목들과 시각 구분 없이 떠있어 헷갈린다는 피드백:
- 외곽을 px-3 py-2.5 rounded-lg border bg-[#1e1b18]/40 박스로 감쌈
- min-w-0 flex-1 라벨 + shrink-0 토글 → 토글이 박스 우측 끝에 명확히 정렬
- aria-label 추가 (스크린리더 보조)

다른 슬라이더 입력과 동일한 박스 패턴이라 시각 일관성 향상.
state/handler 변경 없음.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UX 진단 5가지 문제 중 critical 2가지 해소:
- 시각 위계 부재 (모든 입력 같은 가중치) → Core 강조
- 그룹 헤더 부재 (의미 구분 없음) → SectionLabel 3개

신규:
- src/components/ui/SectionLabel.tsx
  · LucideIcon + title (indigo uppercase) + sub (stone) 한 묶음
  · 외부 mockup 디자인 패턴 채택

좌측 패널 layout 재정렬 — 기존 BASIC|ADVANCED 큰 분리 → 의미 섹션 3개:

1. Core Parameters (필수 분석 대상)
   · 분석 대상 박스 + 업종 박스 — bg-[#1a1816] border-indigo-500/20
     shadow-xl shadow-indigo-500/5 (시각 위계 강조)

2. Operating Constraints (입지·운영·재무 조건)
   · md:2-col grid 평면 배치
   · 슬라이더 4개 (반경/임대료/매장면적/자본금)
   · 객단가/시간대 button group
   · 유동인구 가중치 토글 (col-span-2)
   · 면책 문구는 섹션 마지막에

3. Target Audience (타겟 고객 프로필)
   · 연령/성별/시간/요일 (md:2-col grid)
   · 예상 월매출 (full-width)

원칙 준수:
- 입력 컴포넌트 props/state/onClick 1자도 변경 없음
- 색감/효과/그라디언트 그대로 (HybridSliderInput은 prettier 따옴표 정리만)
- 한 화면 single-form (Stepper 없음)

App.tsx 4760 → 4779줄 (+19, layout wrapper).
tsc + build 통과.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
옵션 패널 UI/UX 고도화(P1 font-mono + P2 border + P3 시각 무게 + 한국어 톤)를
3 에이전트 순차 위임 방식으로 진행하기 위한 design 문서.

결정 사항:
- 분할: 3 에이전트 (P1 / P2+P3 / 한국어 톤)
- 순서: P1 → P2+P3 → 한국어 톤 (안→밖 흐름)
- 게이트: 매 라운드 후 강민 시각 확인

격리 메커니즘:
- 에이전트 간 직접 메시지 금지, git 상태로만 컨텍스트 전달
- 에이전트는 commit/push 권한 없음 (메인 통제)
- 메인이 git diff로 사양 외 변경 검증, 외부 영역 손대면 즉시 롤백

선행 문서: ~/.claude/plans/snug-beaming-truffle.md
다음 단계: writing-plans skill로 implementation plan 작성

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
옵션 패널 UI/UX 고도화 Plan C 3개 작업 영역을 한 commit으로 묶음 적용.
spec(docs/superpowers/specs/2026-04-27-plan-c-subagent-orchestration-design.md)의
3 라운드 분할 의도지만, working tree에 전체 변경이 누적된 상태로 발견되어
B 옵션(1 commit 묶음)으로 처리. 결과 코드는 spec과 동일.

## P1 — font-mono / tabular-nums 일관 적용

HybridSliderInput.tsx:
- 숫자 input에 font-mono tabular-nums 추가 (자릿수 흔들림 방지)
- min/max label에 tabular-nums 보강

App.tsx 13개 영역:
- RechartsDarkTooltip 값 표시 / 예상 월매출 input
- 로딩 spinner % / 카니발 위험 점수
- 위험 점수 / LightGBM·TCN 기여도
- 고령 비율 / 연령 TOP3 share / 평주말 ratio / 브랜드 매칭
- 랜딩 loadProgress %
- 한국어 prose 안 숫자는 의도적 제외 ("초 남음" 등)

## P2 — Border 시스템 통일 (좌측 패널 안만)

- border-[#3a3633] → border-white/5
- 시뮬 결과 패널의 border-stone-800/40, /60은 그대로 유지 (영역 분리 의도)
- Core 박스의 border-indigo-500/20 강조 그대로

## P3 — 시각 무게 균일화 (Operating Constraints)

- 객단가/시간대 button group을 슬라이더와 같은 박스 패턴으로 wrap
  (px-4 py-3 rounded-lg border border-white/5 bg-[#1e1b18]/40)
- 가중치 토글 박스 톤 통일

## SectionLabel 톤 한국어 + 영어 sub

- "Core Parameters" → "핵심 파라미터" / sub="Core Parameters · 필수 항목"
- "Operating Constraints" → "운영 조건" / sub="Operating Constraints · 입지·재무"
- "Target Audience" → "타겟 고객" / sub="Target Audience · 페르소나"

## 원칙 준수
- 입력 컴포넌트 props/state/onClick 1자도 변경 없음
- 색감 본질 유지 (indigo accent, dark stone)
- 한 화면 single-form (Stepper 없음)

tsc + build 통과. 강민 dev 서버 시각 검증 대기.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: agent 설명 클릭 시 나오는 모달이 글로벌 header에 가려짐.

원인 (CSS stacking context):
- DetailModal 자체는 z-[100]
- 그러나 SimulatorDashboard 컨테이너가 absolute inset-0 z-40 → 자체 stacking context 생성
- 그 안의 z-[100]은 외부 z-50(글로벌 header)을 못 이김
- 즉 SimulatorDashboard z-40 layer < 글로벌 header z-50 layer

수정:
- ReactDOM.createPortal(content, document.body)로 모달 렌더
- DOM 트리상 SimulatorDashboard 밖으로 빠져나와 글로벌 header와 형제 stacking
- z-[100] > z-50 정상 동작

부작용 없음 — 모달 트리거(state)와 onClose는 그대로, 위치만 portal로 이동.
SSR safety: typeof document === 'undefined' 가드.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ML 모델(models/revenue_predictor)이 산출하는 monthly_closure_rates(과거 12개월
실측 폐업률)가 응답 스키마에 누락돼 프론트 시각화 불가능했음.

- SimulationOutput.closure_rate 필드 추가
- main.py 응답 매핑 + fallback 응답에도 None 명시

closure_risk(LightGBM+TCN 앙상블 예측)와는 별개의 실측 데이터로,
프론트에서 "과거 추이" vs "위험도 예측"을 명확히 분리 표시 가능.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
본부 영업팀 데모용으로 백엔드는 산출하나 프론트에 표현 안 되던
11개 데이터를 일괄 시각화. 탭별 분배 + 디자인 토큰 통일.

[ForecastTab]
- SHAP: WaterfallChart 제거 → ShapInsightCard 자연어 카드 (사용자 결정)
- BEP 누적이익 회수 곡선 (BepCumulativeProfitChart)
- 시나리오 3종 비교 (ScenariosComparisonChart, 낙관/기본/비관 envelope)
- 거시·트렌드 환경 (TrendSparklinesPanel + TrendDriversRisks + narrative 모달)

[FinancialTab]
- 생존률 ↔ 폐업률 듀얼 KPI (SurvivalRateKpi)
- 과거 12개월 폐업률 추이 LineChart (ClosureRateHistoryChart)
- LightGBM/TCN 폐업위험 피처 기여도 가로 막대 (ClosureSignalsBar, accent prop)

[MarketTab]
- 차별화 포지션 + 기회/리스크 카드 (DifferentiationCard, pull-quote)
- 카니발리제이션 거리 분포 막대 (CannibalizationDistanceChart, sequential color)
- 동 업종 폐업률 추세 카드 (IndustryClosureTrendCard, KPI + Sparkline)

라벨링 원칙: "예측"은 LightGBM+TCN 폐업위험도에만, monthly_closure_rates는
"실측 추이"로 명시 분리.

디자인 토큰: 패널 rounded-3xl/내부 카드 rounded-2xl + bg-stone-900/40, 색상
시멘틱(emerald 양/rose 음/indigo 메인/cyan TCN/amber 거시), Recharts grid
#292524 통일.

types/index.ts: ClosureRate 신규, TrendForecast 확장(samples/key_drivers/risks).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat: 코드 스플릿 Phase A/B/C + 옵션 패널 UI/UX + 11종 차트 + 거짓 양성 fix
…is_excluded_combo

- simulation_months 전면 제거 (input/output 스키마, 타입, 테스트, api-contract)
- simulation_quarters: int | None = None 으로 통일
- BEP 임의종료 제거: 도달 불가(-1) → 40분기, 상한 20 → 40분기
- interface.py default 모델 lstm → tcn
- QuarterlyProjection.is_mock 추가 (A안: 항목별 표시)
- SimulationOutput.is_excluded_combo 추가 (방안 C)
- initial_investment 미사용 필드 제거 (input 스키마, 프론트 요청)
- bep.py docstring 최대 20 → 40분기 업데이트
- api-contract.md quarterly_projection mock 정책 주석 갱신

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
RAG 검색 성능:
- F1: 0.272 → 0.660 (193개 쿼리, top_k=10 기준)
- F1=0 쿼리: 8개 → 0개 (전부 해소)
- 11개 법률 전부 F1 0.5 이상

retriever.py:
- HyDE 동의어 사전 168개 (일상어→법률 키워드)
- _expand_query_hybrid() 함수 (사전+LLM 결합, Redis 캐시, 자동 폴백)
- top_k 기본값 5→10 변경
- Q2Q/Reranker/오답필터 인프라 구축 (비활성, 코드 준비)

prompts.py:
- 3대 이슈 매칭 규칙 (영업지역/필수품목/허위과장)
- Few-shot 3쌍 + HyDE 가상조문 프롬프트

데이터:
- chunks.json 누락 조문 추가 (소방 제24조, 가맹 제12조, 공정거래 제45조/55조/80조, 장애인 제7조)
- golden_set_v3_final.csv (193개 쿼리, 평균 4.3개 정답/쿼리)
- virtual_questions.json (4,551개 예상 질문)
- fail_cases.json (192건 오답 분석)

벤치마크/검수:
- 193개 쿼리 Golden Set + AI 법률 전문가 마킹
- 벤치마크 스크립트 (_bench_193_final.py)
- 자동 마킹 스크립트 (auto_mark_final.py)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
IM3-252: 법률 RAG F1 0.272→0.660 고도화 + HyDE 하이브리드 + 정답지 v3
master(팀장) 로그인 시 본인 이력 + 소속 매니저 이력을 함께 조회.
manager는 기존대로 본인 이력만 접근 가능.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- build_quarterly_projection: Q1~Q4 revenue/CI ÷ store_count (점포 1개 기준)
- build_scenarios: 전 분기 revenue ÷ store_count
- main.py: revenue_forecast.store_count 추출 → 두 함수에 전달

기존 quarterly_projection은 동×업종 전체 합산 분기 매출을 그대로 노출했으나,
이제 점포 1개 기준 분기 평균 예상 매출로 표시됨.
BEP 계산은 _run_bep 내부에서 별도 처리하므로 영향 없음.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- closure_risk/predict.py: tcn_scaler 없을 때 MinMaxScaler fallback 제거
  → 학습 시 저장된 스케일러 파일 필수 요구 (학습·추론 스케일 일치 보장)
- models/interface.py: closure_risk is_mock 여부에 따라 warning/info 분기

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants